package com.aitesting.common;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Parser {
    static public Pattern absolute_http_url_regexp = Pattern.compile("^https?://", Pattern.CASE_INSENSITIVE);

    //use $$ to escape $ notation
    static public Pattern dollar_regex_compile = Pattern.compile("\\$\\$");

    //variable notation, e.g. ${var} or $var
    static public Pattern variable_regex_compile = Pattern.compile("\\$\\{(\\w+)\\}|\\$(\\w+)");

    public static List<String> regex_find_all_variables(String raw_string){

        int match_start_position = 0;
        match_start_position = raw_string.indexOf("$",0);
        List<String> vars_list = new ArrayList<>();
        if (match_start_position == -1){
            return vars_list;
        }
        while (match_start_position < raw_string.length()){
            // $$ > $var

            //search $$
            Matcher dollar_match = dollar_regex_compile.matcher(raw_string);
            if (dollar_match.find(match_start_position)){
                match_start_position = dollar_match.end();
                continue;
            }
            //search variable like ${var} or $var
            Matcher var_match = variable_regex_compile.matcher(raw_string);
            if (var_match.find(match_start_position)){
                String var_name = var_match.group(1) !=null ? var_match.group(1): var_match.group(2);
                vars_list.add(var_name);
                match_start_position = var_match.end();
                continue;
            }
            int curr_position = match_start_position;
            try {
                //find next $ location
                match_start_position = raw_string.indexOf("$",curr_position + 1);
            }catch (Exception e){
                break;
            }
        }
        return vars_list;
    }

    private static Set extract_variables(Object content){
        Set variables = new HashSet();
        if(content instanceof List || content instanceof Set){
            for(Object item : (Collection)content){
                variables.addAll(extract_variables(item));
            }
        }else if (content instanceof Map){
            for(Object value:((Map)content).values()){
                variables.addAll(extract_variables(value));
            }
        }else if (content instanceof String){
            variables.addAll(regex_find_all_variables(Convert.toStr(content)));
        }
        return variables;
    }

    private static Object parse_string(String raw_string,Map<String, Object> variables_mapping){
        int match_start_position = raw_string.indexOf("$", 0);
        StringBuilder parsed_string = new StringBuilder();

        try {
            parsed_string = new StringBuilder(raw_string.substring(0, match_start_position));
        }catch (StringIndexOutOfBoundsException e) {
            return raw_string;
        }
        while (match_start_position < raw_string.length()){
            // 解析优先级：$$ > $var

            // search $$
            Matcher dollar_match = dollar_regex_compile.matcher(raw_string);
            if(dollar_match.find(match_start_position)){
                match_start_position = dollar_match.end();
                parsed_string.append("$");
                continue;
            }

            // search variable like ${var} or $var
            Matcher var_match = variable_regex_compile.matcher(raw_string);
            int end = raw_string.indexOf("$",match_start_position+1);
            end = end == -1 ? raw_string.length() : end;
            if (var_match.region(match_start_position,end).find()){
                String var_name = var_match.group(1) !=null ? var_match.group(1) : var_match.group(2);
                Object var_value = variables_mapping.get(var_name);

                if(raw_string.equals("${var_name}") || raw_string.equals("${" + var_name + "}")){
                    // raw_string is a variable, $var or ${var}, return its value directly
                    return var_value;
                }
                // raw_string contains one or many variables, e.g. "abc${var}def"
                parsed_string.append(Convert.toStr(var_value));
                match_start_position = var_match.end();
                continue;
            }

            int curr_position = match_start_position;
            String remain_string = "";
            try {
                match_start_position = raw_string.indexOf("$",curr_position + 1);
                remain_string = raw_string.substring(curr_position,match_start_position);
            }catch (Exception e){
                remain_string = raw_string.substring(curr_position);
                match_start_position = raw_string.length();
            }
            parsed_string.append(remain_string);
        }
        return parsed_string.toString();
    }

    public static Map<String,Object> parse_variables_mapping(Map<String, Object> variables_mapping) throws BaseException {
        Map<String, Object> parsed_variables = new HashMap<>();
        while (parsed_variables.size() != variables_mapping.size()){
            for(String varName : variables_mapping.keySet()){
                if(parsed_variables.containsKey(varName)){
                    continue;
                }
                Object var_value = variables_mapping.get(varName);
                Set variables = extract_variables(var_value);
                if(variables.contains(var_value)){
//                e.g.
//                variables_mapping = {"token": "abc$token"}
//                variables_mapping = {"key": ["$key", 2]}
                    throw new VariableNotFound(varName);
                }
                List not_defined_variables = new ArrayList();

                for(Object var_name:variables){
                    if(!variables_mapping.containsValue(var_name)){
                        not_defined_variables.add(varName);
                    }
                }
                if(not_defined_variables.size()>0){
                    throw new VariableNotFound(not_defined_variables.toString());
                }
                Object parsed_value = parse_data(var_value, parsed_variables);
                parsed_variables.put(varName, parsed_value);
            }
        }
        return parsed_variables;
    }

    public static Object parse_data(Object raw_data, Map<String, Object> variables_mapping) {
        if(raw_data instanceof String){
            variables_mapping = variables_mapping == null ? new HashMap<>() : variables_mapping;

            raw_data = ((String)raw_data).trim();
            return parse_string((String) raw_data, variables_mapping);
        }else if (raw_data instanceof List) {
            List parsed_data = new ArrayList();
            for(Object obj : ((List)raw_data)){
                Object o = parse_data(obj, variables_mapping);
                parsed_data.add(o);
            }
            return parsed_data;
        }else if (raw_data instanceof Map){
            Map parsed_data = new HashMap();
            Map<String, Object> raw_data_map = (Map<String, Object>)raw_data;

            for(String key : raw_data_map.keySet()){
                Object parsed_key = parse_data(key, variables_mapping);
                Object parsed_vale = parse_data(raw_data_map.get(key),variables_mapping);
                parsed_data.put(parsed_key, parsed_vale);
            }
            return parsed_data;
        }else {
            return raw_data;
        }
    }

    public static String build_url(String base_url, String path){
        Matcher matcher = absolute_http_url_regexp.matcher(path);
        if (matcher.find()){
            return path;
        }else if (base_url != null){
            return StrUtil.format("{}/{}",StrUtil.removeSuffix(base_url,"/"), StrUtil.removePrefix(path,"/"));
        }else {
            return "";
        }
    }

}

